package com.example.demo.aop;

import com.example.demo.annotation.Check;
import com.example.demo.exception.RequiredFieldIsNullException;
import com.example.demo.utils.CheckObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;

/**
 * @version 1.0
 * @Author: LJP
 * @Description: Aop处理类
 * @Date: Created in 17:08 2021/11/30
 */
@Slf4j
@Aspect
@Component
public class CheckRequiredArgsAspect {

    /**
     * 切入点
     * 匹配com.example.demo.controller包及其子包下的所有类的所有方法
     */
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    //@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestBody)")
    public void pointCut() {
    }

    /**
     * 前置通知，目标方法调用前被调用
     */
    @Before("pointCut()")
    public void beforeAdvice() {
    }

    /**
     * 最终通知，目标方法执行完之后执行
     */
    @After("pointCut()")
    public void afterAdvice() {
    }

    /**
     * 后置异常通知
     * 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
     * throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     */
    @AfterThrowing("pointCut()")
    public void afterThrowingAdvice() {
    }

    /**
     * 环绕通知
     * 环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around("pointCut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //todo 查询且参数为实体类时也会检查、不传参时报空指针
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();

        //获得方法参数的所有注解,每个参数的注解为一个数组
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        //判断该方法中是否包含RequestBody注解
        boolean flag = Arrays.stream(parameterAnnotations).anyMatch(item -> Arrays.stream(item).anyMatch(temp -> temp instanceof RequestBody));

        //不包含直接跳过
        if (!flag) {
            return proceedingJoinPoint.proceed();
        }

        //如果包含则进行必填校验
        for (Object arg : proceedingJoinPoint.getArgs()) {
            if (null != arg && null != arg.getClass().getDeclaredAnnotation(Check.class)) {
                Map<Boolean, String> checkResult = CheckObjectUtil.checkFiledIsNull(arg);
                if (null == checkResult.get(true)) {
                    throw new RequiredFieldIsNullException(checkResult.get(false));
                }
            }
        }

        return proceedingJoinPoint.proceed();
    }

}
